import logging
import typing as t
from os.path import splitext

from discord import Embed, Message, NotFound
from discord.ext.commands import Cog

from bot.bot import Bot
from bot.constants import Channels, Filter, URLs

log = logging.getLogger(__name__)

PY_EMBED_DESCRIPTION = (
    "It looks like you tried to attach a Python file - "
    f"please use a code-pasting service such as {URLs.site_schema}{URLs.site_paste}"
)

TXT_LIKE_FILES = {".txt", ".csv", ".json"}
TXT_EMBED_DESCRIPTION = (
    "**Uh-oh!** It looks like your message got zapped by our spam filter. "
    "We currently don't allow `{blocked_extension}` attachments, "
    "so here are some tips to help you travel safely: \n\n"
    "• If you attempted to send a message longer than 2000 characters, try shortening your message "
    "to fit within the character limit or use a pasting service (see below) \n\n"
    "• If you tried to show someone your code, you can use codeblocks \n(run `!code-blocks` in "
    "{cmd_channel_mention} for more information) or use a pasting service like: "
    f"\n\n{URLs.site_schema}{URLs.site_paste}"
)

DISALLOWED_EMBED_DESCRIPTION = (
    "It looks like you tried to attach file type(s) that we do not allow ({blocked_extensions_str}). "
    "We currently allow the following file types: **{joined_whitelist}**.\n\n"
    "Feel free to ask in {meta_channel_mention} if you think this is a mistake."
)


class AntiMalware(Cog):
    """Delete messages which contain attachments with non-whitelisted file extensions."""

    def __init__(self, bot: Bot):
        self.bot = bot

    def _get_whitelisted_file_formats(self) -> list:
        """Get the file formats currently on the whitelist."""
        return self.bot.filter_list_cache['FILE_FORMAT.True'].keys()

    def _get_disallowed_extensions(self, message: Message) -> t.Iterable[str]:
        """Get an iterable containing all the disallowed extensions of attachments."""
        file_extensions = {splitext(attachment.filename.lower())[1] for attachment in message.attachments}
        extensions_blocked = file_extensions - set(self._get_whitelisted_file_formats())
        return extensions_blocked

    @Cog.listener()
    async def on_message(self, message: Message) -> None:
        """Identify messages with prohibited attachments."""
        # Return when message don't have attachment and don't moderate DMs
        if not message.attachments or not message.guild:
            return

        # Ignore webhook and bot messages
        if message.webhook_id or message.author.bot:
            return

        # Check if user is staff, if is, return
        # Since we only care that roles exist to iterate over, check for the attr rather than a User/Member instance
        if hasattr(message.author, "roles") and any(role.id in Filter.role_whitelist for role in message.author.roles):
            return

        embed = Embed()
        extensions_blocked = self._get_disallowed_extensions(message)
        blocked_extensions_str = ', '.join(extensions_blocked)
        if ".py" in extensions_blocked:
            # Short-circuit on *.py files to provide a pastebin link
            embed.description = PY_EMBED_DESCRIPTION
        elif extensions := TXT_LIKE_FILES.intersection(extensions_blocked):
            # Work around Discord AutoConversion of messages longer than 2000 chars to .txt
            cmd_channel = self.bot.get_channel(Channels.bot_commands)
            embed.description = TXT_EMBED_DESCRIPTION.format(
                blocked_extension=extensions.pop(),
                cmd_channel_mention=cmd_channel.mention
            )
        elif extensions_blocked:
            meta_channel = self.bot.get_channel(Channels.meta)
            embed.description = DISALLOWED_EMBED_DESCRIPTION.format(
                joined_whitelist=', '.join(self._get_whitelisted_file_formats()),
                blocked_extensions_str=blocked_extensions_str,
                meta_channel_mention=meta_channel.mention,
            )

        if embed.description:
            log.info(
                f"User '{message.author}' ({message.author.id}) uploaded blacklisted file(s): {blocked_extensions_str}",
                extra={"attachment_list": [attachment.filename for attachment in message.attachments]}
            )

            await message.channel.send(f"Hey {message.author.mention}!", embed=embed)

            # Delete the offending message:
            try:
                await message.delete()
            except NotFound:
                log.info(f"Tried to delete message `{message.id}`, but message could not be found.")


def setup(bot: Bot) -> None:
    """Load the AntiMalware cog."""
    bot.add_cog(AntiMalware(bot))
